import customtkinter
import os
from PIL import Image

from http.server import HTTPServer, SimpleHTTPRequestHandler
from socketserver import ThreadingMixIn

import socket
import time
import threading
from timeit import default_timer as timer

import requests
import json

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

class CustomHTTPRequestHandler(SimpleHTTPRequestHandler):
    def __init__(self, request, client_address, server):
        self.http_path = server.http_path  # 获取并保存根目录
        super().__init__(request, client_address, server)

    def do_GET(self):
        # 如果请求的是根目录，检查是否存在index.html文件
        if self.path == "/":
            index_path = os.path.join(self.http_path, "index.html")
            if os.path.exists(index_path):
                self.path = index_path
        return super().do_GET()

class App(customtkinter.CTk):
    def __init__(self):
        super().__init__()
        self.stop_event = threading.Event()
        self.stop_http_event = threading.Event()
        self.server_thread = None
        self.message_window = None

        self.title("工具箱")
        self.geometry("700x450")
        self.resizable(False, False)

        # set grid layout 1x2
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)

        # load images with light and dark mode image
        image_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "img")
        self.logo_image = customtkinter.CTkImage(Image.open(os.path.join(image_path, "logo.webp")), size=(26, 26))
        self.http_image = customtkinter.CTkImage(Image.open(os.path.join(image_path, "http.webp")))
        self.ping_image = customtkinter.CTkImage(Image.open(os.path.join(image_path, "ping.webp")))
        self.about_image = customtkinter.CTkImage(Image.open(os.path.join(image_path, "about.webp")))
        self.update_image = customtkinter.CTkImage(Image.open(os.path.join(image_path, "update.webp")))

        # navigation frame
        self.navigation_frame = customtkinter.CTkFrame(self, corner_radius=0)
        self.navigation_frame.grid(row=0, column=0, sticky="nsew")
        self.navigation_frame.grid_rowconfigure(4, weight=1)

        self.navigation_frame_label = customtkinter.CTkLabel(self.navigation_frame, text="  工具箱", image=self.logo_image,
                                                             compound="left", font=customtkinter.CTkFont(size=15, weight="bold"))
        self.navigation_frame_label.grid(row=0, column=0, padx=20, pady=20)

        self.http_button = customtkinter.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10, text="HTTP 服务",
                                                   fg_color="transparent", text_color=("gray10", "gray90"), hover_color=("gray70", "gray30"),
                                                   image=self.http_image, anchor="w", command=self.http_button_event)
        self.http_button.grid(row=1, column=0, sticky="ew")

        self.ping_button = customtkinter.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10, text="Ping 指定端口",
                                                      fg_color="transparent", text_color=("gray10", "gray90"), hover_color=("gray70", "gray30"),
                                                      image=self.ping_image, anchor="w", command=self.ping_button_event)
        self.ping_button.grid(row=2, column=0, sticky="ew")

        self.about_button = customtkinter.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10, text="关于",
                                                      fg_color="transparent", text_color=("gray10", "gray90"), hover_color=("gray70", "gray30"),
                                                      image=self.about_image, anchor="w", command=self.about_button_event)
        self.about_button.grid(row=3, column=0, sticky="ew")

        self.chk_update_button = customtkinter.CTkButton(self.navigation_frame, corner_radius=0, height=40, border_spacing=10, text="检查更新",
                                                      fg_color="transparent", text_color=("gray10", "gray90"), hover_color=("gray70", "gray30"),
                                                      image=self.update_image, anchor="w", command=self.chk_update_event)
        self.chk_update_button.grid(row=5, column=0, sticky="ew")

        # http frame
        self.http_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color="transparent")
        self.http_frame.grid_columnconfigure(0, weight=1)

        self.http_port_label = customtkinter.CTkLabel(self.http_frame, text="端口号")
        self.http_port_label.grid(row=0, column=0, padx=20, pady=10)
        self.http_port_entry = customtkinter.CTkEntry(self.http_frame, placeholder_text="80")
        self.http_port_entry.grid(row=0, column=1, padx=(20, 0), pady=(20, 20), sticky="nsew")
        self.http_btn = customtkinter.CTkButton(master=self.http_frame, text="开启 HTTP 服务", fg_color="transparent", border_width=2, text_color=("gray10", "#DCE4EE"), command=self.http_btn_press)
        self.http_btn.grid(row=0, column=2, padx=(20, 20), pady=(20, 20), sticky="nsew")

        self.http_path_label = customtkinter.CTkLabel(self.http_frame, text="服务根目录")
        self.http_path_label.grid(row=1, column=0, padx=20, pady=10)
        self.http_path_entry = customtkinter.CTkEntry(self.http_frame, placeholder_text="\\")
        self.http_path_entry.grid(row=1, column=1, columnspan=2, padx=(20, 0), pady=(20, 20), sticky="nsew")
        # http message box
        self.http_msgbox = customtkinter.CTkTextbox(self.http_frame, height=270)
        self.http_msgbox.grid(row=2, column=0, columnspan=3, padx=(20, 0), pady=(20, 0), sticky="nsew")
        self.http_msgbox.configure(state="disabled")

        # ping frame
        self.ping_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color="transparent")
        self.ping_frame.grid_columnconfigure(0, weight=1)

        self.ping_ip_label = customtkinter.CTkLabel(self.ping_frame, text="目标地址")
        self.ping_ip_label.grid(row=0, column=0, padx=20, pady=10)
        self.ping_ip_entry = customtkinter.CTkEntry(self.ping_frame, placeholder_text="127.0.0.1")
        self.ping_ip_entry.grid(row=0, column=1, padx=(20, 0), pady=(20, 20), sticky="nsew")

        self.ping_port_label = customtkinter.CTkLabel(self.ping_frame, text="端口")
        self.ping_port_label.grid(row=0, column=2, padx=20, pady=10)
        self.ping_port_entry = customtkinter.CTkEntry(self.ping_frame, placeholder_text="80")
        self.ping_port_entry.grid(row=0, column=3, columnspan=2, padx=(20, 0), pady=(20, 20), sticky="nsew")

        self.ping_count_label = customtkinter.CTkLabel(self.ping_frame, text="次数")
        self.ping_count_label.grid(row=1, column=0, padx=20, pady=10)
        self.ping_count_entry = customtkinter.CTkEntry(self.ping_frame, placeholder_text="10")
        self.ping_count_entry.grid(row=1, column=1, padx=(20, 0), pady=(20, 20), sticky="nsew")
        self.ping_btn = customtkinter.CTkButton(master=self.ping_frame, text="开始 Ping", fg_color="transparent", border_width=2, text_color=("gray10", "#DCE4EE"), command=self.ping_btn_press)
        self.ping_btn.grid(row=1, column=3, padx=(20, 20), pady=(20, 20), sticky="nsew")
        # ping message box
        self.ping_msgbox = customtkinter.CTkTextbox(self.ping_frame, height=270)
        self.ping_msgbox.grid(row=2, column=0, columnspan=4, padx=(20, 0), pady=(20, 0), sticky="nsew")
        self.ping_msgbox.configure(state="disabled")

        # about frame
        self.about_frame = customtkinter.CTkFrame(self, corner_radius=0, fg_color="transparent")
        self.about_frame.grid_columnconfigure(0, weight=1)

        self.about_label = customtkinter.CTkLabel(self.about_frame, text="", image=customtkinter.CTkImage(Image.open(os.path.join(image_path, "logo.webp")), size=(100, 100)), compound="top", anchor="w")
        self.about_label.grid(row=0, column=0, padx=20, pady=10)
        self.yi_logo_label = customtkinter.CTkLabel(self.about_frame, text="", image=customtkinter.CTkImage(Image.open(os.path.join(image_path, "yi_logo.webp")), size=(100, 100)), compound="top", anchor="w")
        self.yi_logo_label.grid(row=0, column=1, padx=20, pady=10)

        # about message box
        about_content = """
        version: {}

        ChangeLog:

        - 优化更新流程

        线上文档：https://raoyi.net/toolbox/

        问题反馈：hi@RaoYi.net
        """.format(version)
        
        self.about_textbox = customtkinter.CTkTextbox(self.about_frame, height=270)
        self.about_textbox.grid(row=1, column=0, columnspan=2, padx=(20, 0), pady=(20, 0), sticky="nsew")
        self.about_textbox.insert("0.0", about_content)
        self.about_textbox.configure(state="disabled")

        # select default frame
        self.select_frame_by_name("http")

    def select_frame_by_name(self, name):
        # set button color for selected button
        self.http_button.configure(fg_color=("gray75", "gray25") if name == "http" else "transparent")
        self.ping_button.configure(fg_color=("gray75", "gray25") if name == "ping" else "transparent")
        self.about_button.configure(fg_color=("gray75", "gray25") if name == "about" else "transparent")

        # show selected frame
        if name == "http":
            self.http_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.http_frame.grid_forget()
        if name == "ping":
            self.ping_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.ping_frame.grid_forget()
        if name == "about":
            self.about_frame.grid(row=0, column=1, sticky="nsew")
        else:
            self.about_frame.grid_forget()

    def http_button_event(self):
        self.select_frame_by_name("http")

    def ping_button_event(self):
        self.select_frame_by_name("ping")

    def about_button_event(self):
        self.select_frame_by_name("about")

    def log(self, item, message):
        if item == "http":
            self.http_msgbox.configure(state="normal")
            self.http_msgbox.insert("end", message)
            self.http_msgbox.insert("end", "\n")
            self.http_msgbox.see("end")
            self.http_msgbox.configure(state="disabled")
        elif item == "ping":
            self.ping_msgbox.configure(state="normal")
            self.ping_msgbox.insert("end", message)
            self.ping_msgbox.insert("end", "\n")
            self.ping_msgbox.see("end")
            self.ping_msgbox.configure(state="disabled")

    def http_btn_press(self):
        if self.http_btn.cget("text") == "开启 HTTP 服务":
            self.stop_http_event.clear()
            self.http_ip = socket.gethostbyname(socket.gethostname())
            self.http_port = self.http_port_entry.get()  # 获取端口号
            if not self.http_port:
                self.http_port = self.http_port_entry.cget("placeholder_text")
            try:
                self.http_port = int(self.http_port)
            except ValueError:
                self.log("http", "端口号无效！")
                return

            self.http_path = self.http_path_entry.get()  # 获取根目录
            self.server_thread = threading.Thread(target=self.run_http_server)
            self.server_thread.start()
            self.http_btn.configure(text="停止 HTTP 服务")
            self.log("http", "正在启动 HTTP 服务...")
            #return
        elif self.http_btn.cget("text") == "停止 HTTP 服务":
            if self.server_thread and self.server_thread.is_alive():
                self.httpd.shutdown()
                self.stop_http_event.set()
                self.server_thread.join()
                self.log("http", "HTTP 服务已停止。")
            self.http_btn.configure(text="开启 HTTP 服务")
            #return

    def run_http_server(self):
        server_address = ("", self.http_port)  # 使用空字符串作为 IP 地址，表示监听所有网络接口
        http_path = self.http_path_entry.get()  # 获取用户指定的根目录
        self.httpd = ThreadedHTTPServer(server_address, CustomHTTPRequestHandler)
        self.httpd.http_path = http_path  # 设置根目录属性
        self.log("http", f"Starting HTTP server on http://{self.http_ip}:{self.http_port} with root directory {http_path}")

        def log_message(self, format, *args):
            http_message = format % args
            self.log("http", http_message)

        self.httpd.log_message = log_message

        self.httpd.serve_forever()

    def ping_btn_press(self):
        if self.ping_btn.cget("text") == "开始 Ping":
            threading.Thread(target=self.ping_btn_event, args=(self.stop_event,)).start()
        elif self.ping_btn.cget("text") == "停止 Ping":
            self.stop_event.set()
            self.ping_btn.configure(text="开始 Ping")

    def ping_btn_event(self, stop_event):
        host = self.ping_ip_entry.get()
        if not host:
            host = self.ping_ip_entry.cget("placeholder_text")

        port = self.ping_port_entry.get()
        if not port:
            port = self.ping_port_entry.cget("placeholder_text")
        try:
            port = int(port)
        except ValueError:
            self.log("ping", "端口号无效！")
            return

        count = self.ping_count_entry.get()
        if not count:
            count = self.ping_count_entry.cget("placeholder_text")
        try:
            count = int(count)
        except ValueError:
            self.log("ping", "次数数据无效！")
            return

        self.ping_btn.configure(text="停止 Ping")
        for n in range(1, count + 1):
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.settimeout(1)
                try:
                    start_t = timer()
                    s.connect((host, port))
                    s.shutdown(socket.SHUT_WR)
                    stop_t = timer()

                    s_runtime = 1000 * (stop_t - start_t)

                    ping_msg = "Connected to {}:{}: seq={} time={:.2f} ms".format(host, port, n, s_runtime)
                    self.log("ping", ping_msg)
                except socket.timeout:
                    ping_msg = "Connected to {}:{}: seq={} time out!".format(host, port, n)
                    self.log("ping", ping_msg)
                finally:
                    if stop_event.is_set():
                        self.log("ping", "Ping操作已停止。")
                        self.stop_event.clear()
                        return
                time.sleep(1)
        self.log("ping", "Ping操作完成。")
        self.ping_btn.configure(text="开始 Ping")
        
# update start +++++++++++++++++++++
    # chk version
    def check_for_updates(self, update_info_url):
        response = requests.get(update_info_url)
        if response.status_code == 200:
            latest_version_info = response.json()
            latest_version = latest_version_info.get("latest_version")
            if version != latest_version:
                return latest_version, latest_version_info.get("update_url")
            elif version == latest_version:
                self.chk_update_button.configure(text="已是最新版")
        return None

    # download update files
    def download_update(self):
        self.chk_update_button.configure(text="新版本下载中")
        response = requests.get(new_version[1])
        if response.status_code == 200:
            with open(new_version[1].split("/")[-1], "wb") as file:
                file.write(response.content)
        self.chk_update_button.configure(text="最新版已下载")

    def update_yes_event(self):
        # 关闭子窗口
        if self.message_window:
            self.message_window.destroy()
        self.update_thread = threading.Thread(target=self.download_update)
        self.update_thread.start()

    def chk_update_event(self):
        # check for updates
        global new_version
        new_version = self.check_for_updates(update_info_url)
        if new_version:
            # 创建消息框窗口
            message_window = customtkinter.CTkToplevel(app)
            message_window.geometry("230x100")  # 设置消息框窗口大小
            message_window.title("更新")  # 设置消息框标题
            # 设置窗口置顶
            message_window.winfo_toplevel().attributes("-topmost", True)
            # 在消息框中添加标签显示消息
            message_label = customtkinter.CTkLabel(message_window, text="有新版本 "+new_version[0]+"，点击 OK 更新！")  
            message_label.grid(pady=10, padx=10)
              
            # 在消息框中添加一个OK按钮
            ok_button = customtkinter.CTkButton(message_window, text="OK", command=self.update_yes_event)  
            ok_button.grid(pady=5, padx=10)
            self.message_window = message_window
# update end +++++++++++++++++++++++

if __name__ == "__main__":
    version = "24.04.24"
    update_info_url = "https://gitee.com/raoyi/swupdate/raw/master/sxtoolbox/update_info.json"
    app = App()
    app.mainloop()
